import pygame
import time
import random
import math

pygame.init()

SONG_END = pygame.USEREVENT + 1

white = (255, 255, 255)
black = (0, 0, 0)
blue = (0, 0, 255)
green = (0, 255, 0)
red = (255, 0, 0)

SCREEN_X = 1280
SCREEN_Y = 800
GAME_VIEW_X = 800
GAME_VIEW_Y = 800
FPS = 60
MBLEFT = 1
MBRIGHT = 3

SCREEN = pygame.Rect(0, 0, SCREEN_X, SCREEN_Y)
GAME_VIEW = pygame.Rect(0, 0, GAME_VIEW_X, GAME_VIEW_Y)

gameDisplay = pygame.display.set_mode((SCREEN_X, SCREEN_Y))

pygame.display.set_caption('Tank Game')

image_head = 'head5.png'
image_body = 'body5.png'
image_bullet = 'bullet.png'


clock = pygame.time.Clock()
# font = pygame.font.SysFont(None, 25)
font = pygame.font.Font("LexiGulim090423.ttf", 26)

ADJ_TOP_LEFT = 1
ADJ_TOP_RIGHT = 2
ADJ_TOP_CENTER = 3
ADJ_CENTER_LEFT = 4
ADJ_CENTER_RIGHT = 5
ADJ_CENTER_CENTER = 6
ADJ_BOTTOM_LEFT = 7
ADJ_BOTTOM_RIGHT = 8
ADJ_BOTTOM_CENTER = 9

Text_Adjust = ADJ_CENTER_CENTER
Text_Color = (0, 0, 0)

def normal_angle(ang):
    if ang > 360: ang -= 360
    if ang < 0: ang += 360
    return ang

def diff_angle(ang1, ang2):
    ang = 0
    if ang1 > ang2: ang = ang1 - ang2
    if ang2 > ang1: ang = ang2 - ang1
    return ang

def calc_angle(p1, p2):
    x1, y1 = p1
    x2, y2 = p2
    ang = -math.degrees(math.atan2((y2 - y1), (x2 - x1)))
    ang = normal_angle(ang)
    return ang

def move_angle(x, y, a, m):
    nang = normal_angle(a)
    rcos = math.cos(math.radians(nang))
    rsin = -math.sin(math.radians(nang))
    x += (m * rcos)
    y += (m * rsin)
    return x, y


def spriteSheetToArray( sourceImage, numberRows, numberColumns, transparencyColor):
    imageArray = []
    sourceRect = sourceImage.get_rect()
    spriteWidth = sourceRect.width/numberColumns
    spriteHeight = sourceRect.height/numberRows

    for rows in range(numberRows):
        for cols in range(numberColumns):
            subImage = sourceImage.subsurface( pygame.Rect((spriteWidth*cols, spriteHeight*rows),(spriteWidth,spriteHeight)))
            subImage.set_colorkey( transparencyColor )
            imageArray.append(subImage)

    return imageArray

class Sprite:
    def __init__(self, image_file, sprite_width, sprite_height, trans_color=None):
        self.sprites = []
        image = pygame.image.load(image_file)
        ww = int(image.get_width() / sprite_width)
        hh = int(image.get_height() / sprite_height)
        for y in range(hh):
            for x in range(ww):
                sprite = image.subsurface(pygame.Rect((sprite_width*x, sprite_height*y), (sprite_width, sprite_height)))
                if trans_color is not None:
                    sprite.set_colorkey(trans_color)
                self.sprites.append(sprite)
        self.width = sprite_width
        self.height = sprite_height

    def blit(self, display, index, pos):
        if len(self.sprites) > index:
            display.blit(self.sprites[index], pos)


class Map:
    def __init__(self, map_width, map_height, cell_width, cell_height):
        w = map_width / cell_width
        h = map_height / cell_height
        self.map = []
        self.sprites = []
        image = pygame.image.load(image_file)
        ww = int(image.get_width() / sprite_width)
        hh = int(image.get_height() / sprite_height)
        for y in range(hh):
            for x in range(ww):
                sprite = image.subsurface(pygame.Rect((sprite_width*x, sprite_height*y), (sprite_width, sprite_height)))
                if trans_color is not None:
                    sprite.set_colorkey(trans_color)
                self.sprites.append(sprite)
        self.width = sprite_width
        self.height = sprite_height

    def blit(self, display, index, pos):
        if len(self.sprites) > index:
            display.blit(self.sprites[index], pos)


class Enemy:
    def __init__(self, a, s, b):
        self.pos_x, self.pos_y = move_angle(GAME_VIEW.centerx, GAME_VIEW.centery, a, GAME_VIEW.width * 0.7)
        self.angle = normal_angle(a + 180)
        self.speed = s
        self.bullet = b
        self.life = 255
        self.damage = 10

    def generate(self, a, s, b):
        self.pos_x, self.pos_y = move_angle(GAME_VIEW.centerx, GAME_VIEW.centery, a, GAME_VIEW.width * 0.7)
        self.angle = normal_angle(a + 180)
        self.speed = s
        self.bullet = b
        self.life = 255
        self.damage = 10

    def attack(self, tt):
        attacked = False
        dist = math.hypot(tt.pos_x - self.pos_x, tt.pos_y - self.pos_y)
        if dist < 10:
            tt.life -= self.damage
            self.life -= self.damage
            attacked = True
        return attacked

    def update(self, tick, x, y):
        self.angle = calc_angle((self.pos_x, self.pos_y), (x, y))
        self.pos_x, self.pos_y = move_angle(self.pos_x, self.pos_y, self.angle, tick * self.speed)

    def blit(self, display):
        if GAME_VIEW.collidepoint(self.pos_x, self.pos_y):
            # tx, ty = move_angle(self.pos_x, self.pos_y, self.angle, self.speed)
            c = (self.life, 0, 0)
            pygame.draw.circle(display, c, (int(round(self.pos_x)), int(round(self.pos_y))), 10)
        # rot_bullet = rot_center(self.bullet, normal_angle(self.angle - 90))
        # rc = rot_bullet.get_rect()
        # display.blit(rot_bullet, (self.pos_x - rc.centerx, self.pos_y - rc.centery))

class Bullet:
    def __init__(self, x, y, a, b):
        self.pos_x = x
        self.pos_y = y
        self.angle = a
        self.speed = 120
        self.bullet = b
        self.damage = 5

    def update(self, tick):
        self.pos_x, self.pos_y = move_angle(self.pos_x, self.pos_y, self.angle, tick * self.speed)

    def attack(self, ee):
        attacked = False
        dist = math.hypot(ee.pos_x - self.pos_x, ee.pos_y - self.pos_y)
        if dist < 5:
            ee.life -= self.damage
            attacked = True
        return attacked

    def blit(self, display):
        tx, ty = move_angle(self.pos_x, self.pos_y, self.angle, 5)
        pygame.draw.line(display, red, (self.pos_x, self.pos_y), (tx, ty), 3)
        #rot_bullet = rot_center(self.bullet, normal_angle(self.angle - 90))
        #rc = rot_bullet.get_rect()
        #display.blit(rot_bullet, (self.pos_x - rc.centerx, self.pos_y - rc.centery))


class Tank:
    def __init__(self, head_image, body_image):
        self.head = pygame.image.load(head_image)
        self.head = pygame.transform.scale(self.head, (50, 50))
        self.body = pygame.image.load(body_image)
        self.body = pygame.transform.scale(self.body, (50, 50))
        self.move_speed = 20
        self.head_speed = 60
        self.body_speed = 25
        self.head_angle = 90
        self.body_angle = 90
        self.pos_x = 0
        self.pos_y = 0
        self.move = 0
        self.dest_move_x = 0
        self.dest_move_y = 0
        self.dest_head_x = 0
        self.dest_head_y = 0
        self.dest_head_angle = 90
        self.dest_body_angle = 90
        self.life = 300

    def set_pos(self, x, y):
        self.pos_x = x
        self.pos_y = y
        self.dest_move_x = x
        self.dest_move_y = y

    def set_move_pos(self, x, y):
        self.dest_move_x = x
        self.dest_move_y = y
        self.move = 1

    def set_head_pos(self, x, y):
        self.dest_head_x = x
        self.dest_head_y = y

    def update(self, tick):
        self.rotate_update(tick)
        self.move_update(tick)

    def move_update(self, tick):
        dist = math.hypot(self.dest_move_x - self.pos_x, self.dest_move_y - self.pos_y)
        if dist > 2:
            self.pos_x, self.pos_y = move_angle(self.pos_x, self.pos_y, self.body_angle, tick * self.move_speed)
        else:
            self.pos_x = self.dest_move_x
            self.pos_y = self.dest_move_y
            self.move = 0

    def rotate_update(self, tick):
        self.dest_head_angle = calc_angle((self.pos_x, self.pos_y), (self.dest_head_x, self.dest_head_y))
#        if self.head_speed < diff_angle(self.dest_head_angle, self.head_angle):
        if 3 < diff_angle(self.dest_head_angle, self.head_angle):
            ang = normal_angle(self.dest_head_angle - self.head_angle)
            if ang < 180: self.head_angle += tick * self.head_speed
            else: self.head_angle -= tick * self.head_speed
            self.head_angle = normal_angle(self.head_angle)
        else:
            self.head_angle = self.dest_head_angle

        if self.move:
            self.dest_body_angle = calc_angle((self.pos_x, self.pos_y), (self.dest_move_x, self.dest_move_y))
#            if self.body_speed < diff_angle(self.dest_body_angle, self.body_angle):
            if 3 < diff_angle(self.dest_body_angle, self.body_angle):
                ang = normal_angle(self.dest_body_angle - self.body_angle)
                if ang < 180: self.body_angle += tick * self.body_speed
                else: self.body_angle -= tick * self.body_speed
                self.body_angle = normal_angle(self.body_angle)
            else:
                self.body_angle = self.dest_body_angle

    def debug(self):
        s = 'body angle / dest body angle = ' + repr(self.body_angle) + ' / ' + repr(self.dest_body_angle)
        print (s)
        #print "head angle / dest head angle = %d / %d" % (self.head_angle, self.dest_head_angle)

    def blit(self, display):
        rot_body = rot_center(self.body, normal_angle(self.body_angle - 90))
        rc = rot_body.get_rect()
        display.blit(rot_body, (self.pos_x - rc.centerx, self.pos_y - rc.centery))
        rot_head = rot_center(self.head, normal_angle(self.head_angle - 90))
        display.blit(rot_head, (self.pos_x - rc.centerx, self.pos_y - rc.centery))


def drawtext(rect, msg):
    text_surf = font.render(msg, True, Text_Color)
    text_rect = text_surf.get_rect()

    if Text_Adjust == ADJ_TOP_LEFT:
        text_rect.topleft = rect.topleft
    elif Text_Adjust == ADJ_TOP_RIGHT:
        text_rect.topright = rect.topright
    elif Text_Adjust == ADJ_TOP_CENTER:
        text_rect.midtop = rect.midtop
    if Text_Adjust == ADJ_CENTER_LEFT:
        text_rect.midleft = rect.midleft
    if Text_Adjust == ADJ_CENTER_RIGHT:
        text_rect.midright = rect.midright
    if Text_Adjust == ADJ_CENTER_CENTER:
        text_rect.center = rect.center
    if Text_Adjust == ADJ_BOTTOM_LEFT:
        text_rect.bottomleft = rect.bottomleft
    if Text_Adjust == ADJ_BOTTOM_RIGHT:
        text_rect.bottomright = rect.bottomright
    if Text_Adjust == ADJ_BOTTOM_CENTER:
        text_rect.midbottom = rect.midbottom

    gameDisplay.blit(text_surf, text_rect)


def rot_center(image, angle):
    """rotate an image while keeping its center and size"""
    orig_rect = image.get_rect()
    rot_image = pygame.transform.rotate(image, angle)
    rot_rect = orig_rect.copy()
    rot_rect.center = rot_image.get_rect().center
    rot_image = rot_image.subsurface(rot_rect).copy()
    return rot_image


def game_loop():
    global Text_Adjust, Text_Color
    gameExit = False
    gameOver = False
    mouses = [False, False, False, False, False, False, False, False, False]
    _mx = 0
    _my = 0
    idx = 0
    fps = 1.0 / 30.0
    bullets = []
    enemys = []

    playtime = 0.0

    tank = Tank('head5.png', 'body5.png')
    tank.set_pos(GAME_VIEW.centerx, GAME_VIEW.centery)
    sprites = Sprite('sprites.png', 80, 80)

    enemys.append(Enemy(random.randrange(0, 360), random.randrange(5, 15), 'enemy.png'))
    enemys.append(Enemy(random.randrange(0, 360), random.randrange(5, 15), 'enemy.png'))
    enemys.append(Enemy(random.randrange(0, 360), random.randrange(5, 15), 'enemy.png'))
    enemys.append(Enemy(random.randrange(0, 360), random.randrange(5, 15), 'enemy.png'))

    pygame.mixer.music.set_endevent(SONG_END)
    pygame.mixer.music.load('song.mp3')
    pygame.mixer.music.play()

    while not gameExit:
        milliseconds = clock.tick(FPS)  # milliseconds passed since last frame
        seconds = milliseconds / 1000.0 # seconds passed since last frame (float)
        playtime += seconds

        for event in pygame.event.get():
            #print (event)
            if event.type == pygame.QUIT:
                gameExit = True
            if event.type == SONG_END:
                print("the song ended!")

            if event.type == pygame.MOUSEMOTION:
                _mx, _my = event.pos
                tank.set_head_pos(_mx, _my)
#                tank.debug()

            if event.type == pygame.MOUSEBUTTONDOWN and event.button == MBLEFT:
                mouses[MBLEFT] = True
            if event.type == pygame.MOUSEBUTTONUP and event.button == MBLEFT:
                mouses[MBLEFT] = False
            if event.type == pygame.MOUSEBUTTONDOWN and event.button == MBRIGHT:
                mouses[MBRIGHT] = True
            if event.type == pygame.MOUSEBUTTONUP and event.button == MBRIGHT:
                mouses[MBRIGHT] = False

        keys = pygame.key.get_pressed()
        if mouses[MBRIGHT]:
            tank.set_move_pos(_mx, _my)
        if keys[pygame.K_ESCAPE]:
            gameExit = True
        if keys[pygame.K_SPACE] or mouses[MBLEFT]:
            mx, my = move_angle(tank.pos_x, tank.pos_y, tank.head_angle, 50)
            ang = random.randrange(-150, 151) / 100.0
            ang += tank.head_angle
            bullets.append(Bullet(mx, my, ang, 'bullet.png'))

        gameDisplay.fill(white)
        gameDisplay.fill(green, GAME_VIEW)
        #gameMap.blit(gameDisplay)
        sprites.blit(gameDisplay, idx, (100, 100))
        if (int(round(playtime*100)) % 100) == 0:
            idx = (idx + 1) % len(sprites.sprites)
        #gameDisplay.blit(sprites, (0, 0))

        tank.update(seconds)
        tank.blit(gameDisplay)
        bbs = []
        for bb in bullets:
            bb.update(seconds)
            missed = True
            for ee in enemys:
                if bb.attack(ee):
                    missed = False
                    break
            if missed and 0 < bb.pos_x < GAME_VIEW_X and 0 < bb.pos_y < GAME_VIEW_Y:
                bbs.append(bb)
                bb.blit(gameDisplay)

        bullets = bbs

        for ee in enemys:
            ee.attack(tank)
            if ee.life < 0:
                ee.generate(random.randrange(0, 360), random.randrange(5, 15), 'enemy.png')
            ee.update(seconds, tank.pos_x, tank.pos_y)
            ee.blit(gameDisplay)

        pygame.display.update()

    Text_Adjust = ADJ_TOP_CENTER
    Text_Color = (255, 0, 0)
    drawtext(GAME_VIEW, "Game End")
    pygame.display.update()
    time.sleep(2)

game_loop()

pygame.quit()
quit()
